/*
 * Copyright 2010, Red Hat, Inc. and individual contributors as indicated by the
 * @author tags. See the copyright.txt file in the distribution for a full
 * listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This software is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
 * site: http://www.fsf.org.
 */
package org.zanata.tmx;

import java.io.InputStream;

import javax.persistence.EntityExistsException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import nu.xom.Element;

import org.hibernate.CacheMode;
import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.transaction.Transaction;
import org.zanata.common.util.ElementBuilder;
import org.zanata.model.tm.TransMemory;
import org.zanata.util.TMXParseException;
import org.zanata.xml.TmxDtdResolver;

/**
 * Parses TMX input.
 *
 * @author Carlos Munoz <a
 *         href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
 */
@Name("tmxParser")
@AutoCreate
@Slf4j
public class TMXParser {
    // Batch size to commit in a new transaction for long files
    private static final int BATCH_SIZE = 100;

    @In
    private Session session;
    @In
    private TransMemoryAdapter transMemoryAdapter;

    @Transactional
    public void parseAndSaveTMX(InputStream input, TransMemory transMemory)
            throws TMXParseException, SecurityException, IllegalStateException,
            RollbackException, HeuristicMixedException,
            HeuristicRollbackException, SystemException, NotSupportedException {
        int handledTUs = 0;
        try {
            log.info("parsing started for: {}", transMemory.getSlug());
            session.setFlushMode(FlushMode.MANUAL);
            session.setCacheMode(CacheMode.IGNORE);
            XMLInputFactory factory = XMLInputFactory.newInstance();
            factory.setProperty(XMLInputFactory.SUPPORT_DTD, true);
            factory.setProperty(XMLInputFactory.IS_VALIDATING, true);
            factory.setXMLResolver(new TmxDtdResolver());
            @Cleanup
            XMLStreamReader reader = factory.createXMLStreamReader(input);

            QName tmx = new QName("tmx");
            QName header = new QName("header");
            QName tu = new QName("tu");

            while (reader.hasNext()
                    && reader.next() != XMLStreamConstants.START_ELEMENT) {
            }
            if (!reader.hasNext())
                throw new TMXParseException("No root element");
            if (!reader.getName().equals(tmx))
                throw new TMXParseException("Wrong root element: expected tmx");

            // At this point, event = START_ELEMENT and name = tmx
            while (reader.hasNext()) {
                int eventType = reader.next();
                if (eventType == XMLStreamConstants.START_ELEMENT) {
                    if (handledTUs > 0 && handledTUs % BATCH_SIZE == 0) {
                        commitBatch(handledTUs);
                    }
                    QName elemName = reader.getName();
                    if (elemName.equals(tu)) {
                        Element tuElem = ElementBuilder.buildElement(reader);
                        transMemoryAdapter
                                .processTransUnit(transMemory, tuElem);
                        handledTUs++;
                    } else if (elemName.equals(header)) {
                        Element headerElem =
                                ElementBuilder.buildElement(reader);
                        transMemoryAdapter.processHeader(transMemory,
                                headerElem);
                    }
                }
            }
            commitBatch(handledTUs); // A new transaction is needed for Seam to
                                     // commit it
        } catch (EntityExistsException e) {
            String msg =
                    "Possible duplicate TU (duplicate tuid or duplicate"
                            + "src content without tuid)";
            throw new TMXParseException(msg, e);
        } catch (XMLStreamException e) {
            throw new TMXParseException(e);
        } finally {
            log.info("parsing stopped for: {}, TU count={}",
                    transMemory.getSlug(), handledTUs);
        }
    }

    private void commitBatch(int numProcessed) throws SecurityException,
            IllegalStateException, RollbackException, HeuristicMixedException,
            HeuristicRollbackException, SystemException, NotSupportedException {
        session.flush();
        session.clear();
        Transaction.instance().commit();
        Transaction.instance().begin();
    }

}
